#ifndef _H_IACOMM
#define _H_IACOMM

#include <stdint.h>
#include <NewSoftSerial.h>

/** Header Protocol

Format:

header = (H, M, C#, C#-rcvd, M-Arg-num, M-arg-crc)
  H = special seq = 0xAA
  M = Method id [0-255]
  C# = num of command sent [0-255]
  C#-recvd = num of last command recvd [0-255]
  M-Arg-num = # of M-Arg bytes (< 2^16)
  M-arg-crc = crc of M-arg bytes

  Special methods

   M=
  0. SYNC, => header = [0xAA, 0, C#-sent, C#-recvd, 0]
  0. END_SPECIAL

  Every cmd is synced. (optimize later)
  upper layer decides if need to wait for syncResponse.
  
  For every cmd, just send, wait for configurable interval for
  reliable comm before returning failure.

  user can decide to call sync() if needed.

  sync & syncResponse:
  Every network send gets a syncResponse.
  A syncResponse has CRC = 1.
  A syncResponse for a data chunk responds with dataSize.
  A sync gets a syncResponse in return.
  A sync command has CRC = 0

**/

typedef uint8_t IAhdrBuf_t[8];
class IAcomm
{

public:
  static uint32_t SYNC_WAIT_TIME; // 10000 by default.
  static uint32_t CMD_RESPONSE_WAIT_TIME; // 1000 by default.

public:
  enum {SYNC_WAIT_TIME_DEF=10000};
  enum {CMD_RESPONSE_WAIT_TIME_DEF=1000};

  enum {Marker = 0xAA};
  enum Commands {
    M_SYNC	= 0,
    M_START_USER  = 1		// User methods can start from here.
  };

private:
  NewSoftSerial peer;		// Communication peer

  // send data
  uint8_t methodId;		// id of method
  uint8_t cmdSent;		// num of commands sent, start with 1.
  uint8_t cmdRcvd;		// num of commands rcvd
  uint16_t numArgBytes;		// num bytes in method argument
  uint16_t argCRC;		// CRC of arg, can be 0 if don't care.

  // peer data
  uint8_t peerMethodId;		// id of method from peer
  uint8_t peerCmdSent;		// peer's cmd sent
  uint8_t peerCmdRcvd;		// peer's command rcvd
  uint16_t peerDataBytes; 	// how much data peer has recvd.
  uint16_t peerCRC;

  // temp header
  IAhdrBuf_t hdrBuf;		// Flattened header 

  // common data
  uint8_t* dataBuf;		// send/receive data buffer
  uint16_t lenBuf;		// data length sent/received so far.

  // Comm state
  enum {NONE=0,SYNC,READING,WRITING} commStatus;

public:
  IAcomm(uint8_t rxPin, uint8_t txPin);
  
  // Network methods

  /**
     invoke a remote method
     @param method remote method
     @param data   argument to method, can be a NULL pointer
     @param len    length of data, can be 0.
     @param waitMs how many ms to wait. -1 for forever, 0 not at all.
     @return if waiting, return true if got sync, false otherwise.
             if not waiting, return true always.
  */
  bool send(uint8_t method, uint8_t* data, uint16_t length,
	    int32_t waitMs=CMD_RESPONSE_WAIT_TIME_DEF);
  
  /**
     recv() receive a remote method invocation
     @param method local method that is invoked
     @param length data length which accompanies this request.
     @param waitMs how many ms to wait. -1 for forever, 0 not at all.
     @return return true if got data, false otherwise.
  */
  bool recv(uint8_t& method, uint16_t& length,
	    int32_t waitMs=0);

  /**
     recvData() read data from network for the current method
     @param data data buffer, should have storage.
     @param waitMs how many ms to wait. -1 for forever, 0 not at all.
     @return return true if got data, false otherwise.
  */
  bool recvData(uint8_t* data,int32_t waitMs=0); 

  /**
     sync with network. for initialization and recovery.
     @param waitMs how many ms to wait. -1 for forever, 0 not at all.
     @return return true if synced, false otherwise.
  */
  bool sync(int32_t waitMs=SYNC_WAIT_TIME_DEF);

  inline void begin(uint16_t speed) {peer.begin(speed);}

 private:

  void serialCheck();

  // simple write. no wait.
  inline void writeHdr2Net();
  inline bool methodIsSync() {
    return (peerMethodId == SYNC);}

  inline bool methodIsSyncResponse() {
    return (peerMethodId == SYNC && peerCRC == 1);}

  inline bool readValidReq();

  // send data down the wire and wait for sync response.
  bool writeData2Net(int32_t deadline);

  // set my CRC to send it down the wire.
  void CRC();

  // check peer's CRC against data.
  bool checkCRC();

  // Set the fields of class and fill up buffer
  void setData(uint8_t method, 		// method
	       uint16_t numData,	// num data arg bytes.
	       uint8_t * data,
	       bool syncResponse=false); // true if syncResponse

  // sendSync
  void sendSync();
  
  // send a response to a sync command
  void sendSyncResponse();

  // deadline can be 0 for don't wait, -1 for wait forever.
  bool waitForSyncResponse(int32_t deadline);

  // header read.
  bool readHdr();
  
  // deadline is -1 for forever, 0 for immediate.
  bool readData(int32_t deadline);
   
  // fill buffer from fields
  void fields2buf();

  // Set with buffer 
  uint8_t buf2fields();	// return 0 on failure, 1 on success


}; 

  
 
#endif
